Creating IPython Widgets (IPywidgets)¶
ipywidgets is a Python package that can be installed using ``pip install ipywidgets``. Be sure to have the latest version of ipywidgets (at least 7.0). ## Introduction IPython widgets can be used to enhance the interactivity of Jupyter notebooks. They allow adding GUI-like elements, such as buttons, textboxes, tabs, that can be linked to Python code execution. Widgets can be further linked to Javascript code, an important feature as it allows modification of the Javascript front-end. This enables: - Adding new elements using Javascript. - Cell manipulation (creating/deleting/moving/executing cells) - Linking keyboard shortcuts to code execution - Changing/adding CSS to change the page style
IPywidgets can be used to create very interesting interactive features. As an example, one could create a sidebar with buttons that, when pressed, execute specific notebook cells.
For more information, please read: - IPywidgets documentation: https://ipywidgets.readthedocs.io/en/stable/. In particular: - List of standard IPywidgets: https://ipywidgets.readthedocs.io/en/stable/examples/Widget%20List.html - IPywidget interaction with Javascript: https://ipywidgets.readthedocs.io/en/stable/examples/Widget%20Custom.html - Jupyter notebook extensions https://github.com/ipython-contrib/jupyter_contrib_nbextensions These extensions are written in Javascript, and so the code can also be used for IPywidgets. The big difference is that a notebook extension is always executed when a notebook starts (if it’s enabled), while an IPywidget needs to be created in Python. Furthermore, an extension is limited to Javascript, but an IPywidget can have both Python and Javascript, and can interact between the two. It contains many good examples; for a good overview, install https://github.com/Jupyter-contrib/jupyter_nbextensions_configurator - QCoDeS used to have widgets as well, which showed an interesting way of using them. Check version 1.3 (hash fb3c964c8c64cc81af6e9dee181ba0b3ab45a0c0)
Creating an IPywidget¶
There are four ways (that I know of) to create an IPywidget. Each of these ways is explained below
Standard IPywidgets (no Javascript)¶
The simplest method to use widgets is to import ipywidgets and use its standard widget objects. As an example, we create a button that, when presed, will print some text.
[39]:
from ipywidgets import widgets
from IPython.display import display
button = widgets.Button(description='click me')
# Attach callback when pressed
button.on_click(lambda event: print('You clicked me'))
# Display button
display(button)
A Jupyter Widget
This already provides quite some interactivity, as it allows execution of Python code when events occur. The advantage is that the code is very simple, and so it’s easy to create such widgets. There are several disadvantages though: - It is difficult to interact these elements with Javascript (you would need to subclass it) - The widgets can only be modified by a limited amount (no real css modification)
Therefore, this method is only useful for very simple widgets. However, you can add these widgets as part of larger widgets created using the methods described below.
More info: https://ipywidgets.readthedocs.io/en/stable/examples/Widget%20Basics.html
Method 1: Creating Javascript cell¶
Jupyter notebook allows direct execution of javascript code by starting the code with %%javascript
. As an example, the code below sends a message to the console
[6]:
%%javascript
console.log('this is a console message.')
<IPython.core.display.Javascript object>
Console messages can be read by going to the browser console, usually in developer tools (press F12 for chrome in windows).
We will go through a small example of a custom widget. This shows the interaction between the Javascript front-end and the Python back-end. The example is based on the IPywidgets documentation, and shows additional information: https://ipywidgets.readthedocs.io/en/stable/examples/Widget%20Custom.html
Javascript front-end¶
Python widgets can be linked to a Javascript module. These module use the AMD mechanism, used to load modules asynchronously. These AMD modules are defined with the define
command, where the first argument is the name, and the second is the list of required packages. These packages are passed as arguments to the function (in this case widgets).
An IPywidget is connected to a specific view within a single module (in the example below HelloView in the hello module).
[38]:
%%javascript
// This line is needed to ensure that the module does not already exist
require.undef('hello');
define('hello', ["@jupyter-widgets/base"], function(widgets) {
var HelloView = widgets.DOMWidgetView.extend({
render: function() {
this.value_changed();
// We specify a Javascript callback when the `value` attribute changes value
this.model.on('change:value', this.value_changed, this);
},
value_changed: function() {
// Change the text output every time the value attribute changes
this.el.textContent = this.model.get('value');
},
});
return {
HelloView : HelloView
};
});
<IPython.core.display.Javascript object>
Some packages are already automatically loaded, and therefore don’t need to be explicitly passed. For instance, Jupyter
can be used to interact with the Jupyter notebook (create cells etc.), and $
is used for jQuery
Python back-end¶
A custom Ipywidget can be created by inheriting from widgets.DOMWidget. Below, we see one such widget, which three attributes. These are special attributes, as they are traitlet instances. The tag sync=True ensures that they are linked to the Javascript front-end, allowing communication between the two.
The _view_module
attribute specifies the widget module, and the _view_name
specifies the Javascript View within the module. The value
attribute is used to interact between the Python backend and the Javascript front-end
[34]:
from traitlets import Unicode
class HelloWidget(widgets.DOMWidget):
_view_name = Unicode('HelloView').tag(sync=True)
_view_module = Unicode('hello').tag(sync=True)
value = Unicode('Hello World!').tag(sync=True)
[41]:
# Here we instantiate the widget and display it
hello_widget = HelloWidget()
display(hello_widget)
A Jupyter Widget
[44]:
# When we change the value attribute, this immediately changes the text above (via Javascript)
hello_widget.value = 'Newer text'
event changed
We’ve seen above that a change in value can trigger a Javascript callback. We can also register a Python callback as follow:
[45]:
hello_widget.observe(lambda event: print('event changed'))
[46]:
# Changing the value now also emits a print statement
hello_widget.value = 'Newest text'
event changed
event changed
CSS¶
It’s worth mentioning that the Javascript front-end can be combined with CSS. I really don’t know much about CSS, but the sidebar_toc repository contains an example.
Also important is that anything created with Javascript/CSS (such as buttons) allows much more customizability. For more complex widgets, it’s therefore recommended to create all the buttons etc. in javascript+CSS, and only have the Python backend to allow the user to interact with it through Python code.
Creating and executing files¶
Although the above method provides an easy way to create a new widget and test it, once it is working you don’t want all this code remaining in your notebook. A better approach is to create files for Javascript, Python, (and possibly CSS). IPython allows you to execute arbitrary chunks of Javascript, HTML (including CSS), and others:
[47]:
from IPython.display import Javascript, HTML
QCoDeS contains a piece of code that allows easy execution of Javascript/CSS/HTML files, if they are placed within the QCoDeS folder. For instance, the following code would execute all the Javascript within qcodes/widgets/sidebar_widget.js
[48]:
from qcodes.widgets.display import display_auto
display_auto('widgets/sidebar_widget.js')
For QCoDeS-specific widgets, it is therefore recommended to place the widgets inside the qcodes/widgets folder, and execute it this way. See the sidebar widget for an example.
Creating custom package¶
If you have a widget that would be useful outside of QCoDeS as well, you could decide to create a package for it that you can import. This does take some time and effort, and is not recommended for things QCoDeS related.
There is a template for such packages here: https://github.com/jupyter-widgets/widget-cookiecutter There are a few annoying differences when using this template. Most importantly, the template does not use the AMD define method. Also, installing a widget package can be complicated, as it requires installation of npm
If you want an example of making a custom widget package, see https://github.com/nulinspiratie/cell-tab-widget
Jupyter notebook extensions¶
Although not a widget, a notebook extension does allow adding arbitrary javascript code. Therefore, if you don’t need any communication with the Python backend, it might be worthwhile to create an extension instead.